/*
 * This file is part of JFlowMap.
 *
 * Copyright 2009 Ilya Boyandin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package fons.navigator.util.piccolo;

import java.awt.Color;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;

import edu.umd.cs.piccolo.PCamera;
import edu.umd.cs.piccolo.event.PBasicInputEventHandler;
import edu.umd.cs.piccolo.event.PInputEvent;
import edu.umd.cs.piccolox.nodes.PLine;
import edu.umd.cs.piccolox.util.LineShape;
import edu.umd.cs.piccolox.util.PFixedWidthStroke;
import fons.navigator.views.piemap.PGeoMap;

/**
 * @author Ilya Boyandin
 */
public class Lasso extends PBasicInputEventHandler {

	private static final Stroke LINE_STROKE = new PFixedWidthStroke(2.0f);
	private static final double MIN_DIST_BETWEEN_SUBSEQUENT_POINTS = .0;  // on screen

	private final PCamera camera;
	private final PLine line;
	private Point2D lastPos;
	private Area prevSelectionArea;
	private boolean wasDragged;

	/**
	 * @param camera Node to which the lasso node will be added.
	 */
	public Lasso(PCamera camera, Color lineColor) {
		this.camera = camera;
		line = new PLine(null, LINE_STROKE);
		line.setStrokePaint(lineColor);
		//    setEventFilter(new PInputEventFilter() {
		//      @Override
		//      public boolean acceptsEvent(PInputEvent event, int type) {
		//        return event.isControlDown();
		//      }
		//    });
	}

	public boolean isSelecting() {
		return line.getPointCount() > 0;
	}

	@Override
	public void mouseReleased(PInputEvent event) {
		if (inTarget(event)) {
			if (isSelecting()  ||
					(!wasDragged  &&  !event.isControlDown()  &&
							(event.getPickedNode() instanceof PGeoMap))) {  // to support "clear selection"
				stop(event);
			}
		}
		wasDragged = false;
	}

	/**
	 * @param shape In camera's local coords
	 */
	public void selectionMade(Shape shape) {
		// to be overriden
	}

	//  @Override
	@Override
	public void mouseMoved(PInputEvent event) {
		if (isSelecting()  &&  !event.isControlDown()  &&  !event.isLeftMouseButton()) {
			stop(event);
		}
	}

	//  @Override
	//  public void mouseExited(PInputEvent event) {
	//    if (event.getPickedNode() == camera) {
	//      clear();
	//    }
	//  }

	@Override
	public void mouseDragged(PInputEvent event) {
		if (inTarget(event)) {
			if (event.isControlDown()) {
				Point2D posInCanvas = event.getCanvasPosition();
				int numPoints = line.getPointCount();
				if (numPoints == 0) {
					start();
				}
				if (numPoints == 0   ||  posInCanvas.distance(lastPos) > MIN_DIST_BETWEEN_SUBSEQUENT_POINTS) {
					lastPos = (Point2D) posInCanvas.clone();  // copy to be stored
					Point2D pos = camera.globalToLocal((Point2D)posInCanvas.clone());
					if (numPoints == 0) {
						line.addPoint(numPoints, pos.getX(), pos.getY());
						line.addPoint(numPoints + 1, pos.getX(), pos.getY());
					} else {
						line.addPoint(numPoints - 1, pos.getX(), pos.getY());
					}
				}
			} else {
				//        stop(event);
			}
			wasDragged = true;
		}
	}

	private boolean inTarget(PInputEvent event) {
		return true;
		//    return camera.localToGlobal(camera.getBounds()).contains(event.getCanvasPosition());
	}

	private void start() {
		camera.addChild(line);
	}

	private void stop(PInputEvent event) {
		Area area = asArea(line.getLineReference());
		if (event.isShiftDown()) {
			if (prevSelectionArea != null) {
				area.add(prevSelectionArea);
			}
		} else if (event.isAltDown()) {
			if (prevSelectionArea != null) {
				prevSelectionArea.subtract(area);
			}
			area = prevSelectionArea;
		}
		selectionMade(area);
		prevSelectionArea = area;
		clear();
	}

	public void clear() {
		camera.removeChild(line);
		line.removeAllPoints();
		lastPos = null;
		wasDragged = false;
	}

	public Area asArea(LineShape line) {
		GeneralPath path = new GeneralPath();
		int numPoints = line.getPointCount();
		if (numPoints > 0) {
			Point2D.Double p = new Point2D.Double(line.getX(0), line.getY(0));
			camera.localToView(p);
			path.moveTo(p.x, p.y);
			for (int i = 0; i < numPoints; i++) {
				p.setLocation(line.getX(i), line.getY(i));
				camera.localToView(p);
				path.lineTo(p.x, p.y);
			}
		}
		return new Area(path);
	}
}
